﻿using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpLogging;
using Serilog.Context;

namespace LogExtensions
{
    public class HttpLogMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<HttpLogMiddleware> _logger;
        //private readonly IAuthUser _authUser;
        private readonly LogConfig _logConfig;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="next"></param>
        /// <param name="serviceProvider"></param>
        /// <param name="logger"></param>
        /// <param name="appConfig"></param>
        public HttpLogMiddleware(RequestDelegate next,
            ILogger<HttpLogMiddleware> logger, IOptionsMonitor<LogConfig> logConfig)
        {
            _next = next;
            _logger = logger;
            _logConfig = logConfig.CurrentValue;
            //_authUser = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IAuthUser>();
        }


        private Encoding GetEncoding(string? contentType)
        {
            var mediaType = contentType == null ? default(MediaType) :
                new MediaType(contentType);
            var encoding = mediaType.Encoding;
            if (encoding == null)
                encoding = Encoding.UTF8;
            return encoding;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task InvokeAsync(HttpContext context)
        {

            var path = context.Request.Path.ToString();
            if (!_logConfig.HttpLog)
            {
                await _next(context);
            }
            else if (context.Request.Method.ToLower() == "options")
            {
                await _next(context);
            }
            else if (context.Request.Path != null
        && ((_logConfig.Includes != null && !_logConfig.Includes.Any(x => path.ToLower().Contains(x.ToLower())))
        || (_logConfig.Excludes != null && _logConfig.Excludes.Any(x => path.ToLower().Contains(x.ToLower())))
             ))
            {
                //不需要日志的直接跳出  StatusCodes
                await _next(context);
            }
            else
            {



                if (context.Request.Method.ToLower() != "options")
                {
                    if (context.Request.Body.CanRead)
                        context.Request.EnableBuffering();
                }

                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                string responseBody;
                Stream originalBodyStream = context.Response.Body;
                using (var memoryStream = new MemoryStream())
                {
                    context.Response.Body = memoryStream;
                    try
                    {
                        await _next(context);
                    }
                    //catch (Exception ex)
                    //{

                    //    await HandleExceptionAsync(context, ex);
                    //}
                    finally
                    {
                        var en = GetEncoding(context.Response.ContentType);
                        responseBody = await GetResponseBody(context.Response.Body, en);

                        await memoryStream.CopyToAsync(originalBodyStream);
                    }
                }
                stopwatch.Stop();
                var sw = stopwatch.ElapsedMilliseconds;
                //stopwatch.Restart();
                //  endpoint = GetEndpoint(context);
                var loggingAttribute = context.GetEndpoint()?.Metadata.GetMetadata<HttpLoggingAttribute>();
                var desc = context.GetEndpoint()?.Metadata.GetMetadata<ControllerActionDescriptor>();
                if (desc != null)
                {
                    var requestBody = "";
                    if (context.Request.Body.CanRead && context.Request.Body.CanSeek)
                    {
                        context.Request.Body.Position = 0;
                        using var requestReader = new StreamReader(context.Request.Body);
                        requestBody =await requestReader.ReadToEndAsync();
                        context.Request.Body.Position = 0;
                    }
                    var statusCode = context.Response.StatusCode;
                    if (context.Items.TryGetValue("code", out object? code))
                    {
                        if (code != null && code is int cd)
                        {
                            statusCode = cd;
                        }
                    }
                    _logger.LogInformation("\r\n{name} \r\nDuration:{Duration}ms \r\nIpAddress:{IpAddress} \r\nHost:{Host}\r\nPath:{Path} \r\nMethod:{Method} \r\nQueryString:{QueryString} \r\nRequestBody:{RequestBody} \r\nUserId:{UserId}\r\nResponseBody:{ResponseBody} \r\nStatusCode:{StatusCode} \r\ncode:{code}"
                        , "http", sw, context.Connection.RemoteIpAddress?.ToString() ?? "", context.Request.Host.ToString(),
                        path, context.Request.Method, context.Request.QueryString.ToString(), requestBody,
                        context.User.FindFirst("dn")?.Value ?? context.User.Identity?.Name,
                        responseBody, context.Response.StatusCode.ToString(), statusCode
                        );
                }



            }

        }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public Endpoint? GetEndpoint(HttpContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            return context.Features.Get<IEndpointFeature>()?.Endpoint;
        }

        private async Task<string> GetResponseBody(Stream body, Encoding en)
        {
            body.Seek(0, SeekOrigin.Begin);

            string bodyString = await new StreamReader(body, en).ReadToEndAsync();
            body.Seek(0, SeekOrigin.Begin);

            return bodyString;
        }




    }
}
